import json
import threading
import socket
import select

from gglib.packet import Packet

EV_CONNECTED = 1
EV_READ = 2
EV_DISCONNECTED = 3


class AioClient(threading.Thread):
    def __init__(self, address, cb, *args, **kwargs):
        self._event_callback = cb
        self._address = address
        super().__init__(*args, **kwargs)
        self.daemon = True
        self.start()

    def _connect(self):
        # 连接失败时，等待重新连接
        while True:
            sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
            try:
                sock.connect(self._address)
            except:
                import time
                time.sleep(1)
            else:
                self._event_callback(self, EV_CONNECTED)
                # self._delegate.on_connect(self)
                return sock

    def _loop(self, sock):
        # 使用IO复用读取数据。
        while True:
            try:
                _ready_fds, _, _ = select.select([sock], [], [])
                for fd in _ready_fds:
                    try:
                        data = fd.recv(65535)
                        if not data:
                            raise ConnectionError
                        self._event_callback(self, EV_READ, data)
                    except ConnectionError:
                        self._event_callback(self, EV_DISCONNECTED)
                        break
            except:
                break

    def run(self):
        while True:
            self.sock = self._connect()
            self._loop(self.sock)
            self.sock.close()

    def send(self, data):
        self.sock.send(data)


class GGChatDelegate:
    def on_connected(self, client):
        raise NotImplementedError

    def on_disconnected(self, client):
        raise NotImplementedError

    def on_logged(self, client, **kwargs):
        raise NotImplementedError

    def on_messaged(self, client, **kwargs):
        raise NotImplementedError

    def on_logout(self, client):
        raise NotImplementedError

    def on_exception(self, exception):
        raise NotImplementedError


class NullDelegate(GGChatDelegate):

    def on_connected(self, client):
        pass

    def on_disconnected(self, client):
        pass

    def on_logged(self, client, **kwargs):
        pass

    def on_messaged(self, client, **kwargs):
        pass

    def on_logout(self, client):
        pass

    def on_exception(self, exception):
        pass


class GGChatClient:
    def __init__(self, address, username, password, delegate=NullDelegate()):
        self.username = username
        self.password = password
        self._aio_connect = AioClient(cb=self._event_callback, address=address)
        self._delegate = delegate

    @property
    def delegate(self):
        return self._delegate

    @delegate.setter
    def delegate(self, delegate):
        assert isinstance(delegate, GGChatDelegate)
        self._delegate = delegate

    def _event_callback(self, io, event, data=None, *args, **kwargs):
        if event == EV_CONNECTED:
            self._delegate.on_connected(self)
        elif event == EV_DISCONNECTED:
            self._delegate.on_disconnected(self)
        elif event == EV_READ:
            json_obj = json.loads(data.decode("utf-8"))
            command = json_obj["command"]
            if command == "login":
                self._delegate.on_logged(self, **json_obj)
            elif command == "message":
                self._delegate.on_messaged(self, **json_obj)
            else:
                raise Exception("Unknown command:" + command)

    def _send_packet(self, pkt):
        try:
            self._aio_connect.send(bytes(pkt))
        except Exception as e:
            self._delegate.on_exception(e)

    def login(self, username, password):
        self.username = username
        self.password = password
        pkt = Packet()
        pkt["command"] = "login"
        pkt["username"] = self.username
        pkt['password'] = self.password
        self._send_packet(pkt)
        # self._aio_connect.send(bytes(pkt))

    def send_message(self, receiver, message, type="text"):
        pkt = Packet()
        pkt["command"] = "message"
        pkt["receiver"] = receiver
        pkt['message'] = message
        pkt['type'] = type
        self._send_packet(pkt)
        # self._aio_connect.send(bytes(pkt))

    def logout(self):
        pkt = Packet()
        pkt["command"] = "logout"
        self._send_packet(pkt)
        # self._aio_connect.send(bytes(pkt))

# aio = AioClient(("192.168.3.4", 6666), aio_callback)
# TODO: do others
